/*
// Copyright (c) 2018 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
*/

#include "ExitAirTempSensor.hpp"

#include "SensorPaths.hpp"
#include "Thresholds.hpp"
#include "Utils.hpp"
#include "VariantVisitors.hpp"
#include "sensor.hpp"

#include <boost/algorithm/string/replace.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/post.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/container/flat_map.hpp>
#include <phosphor-logging/lg2.hpp>
#include <sdbusplus/asio/connection.hpp>
#include <sdbusplus/asio/object_server.hpp>
#include <sdbusplus/bus.hpp>
#include <sdbusplus/bus/match.hpp>
#include <sdbusplus/message.hpp>

#include <algorithm>
#include <array>
#include <chrono>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <limits>
#include <memory>
#include <stdexcept>
#include <string>
#include <utility>
#include <variant>
#include <vector>

constexpr const double altitudeFactor = 1.14;
constexpr const char* exitAirType = "ExitAirTempSensor";
constexpr const char* cfmType = "CFMSensor";

// todo: this *might* need to be configurable
constexpr const char* inletTemperatureSensor = "temperature/Front_Panel_Temp";
constexpr const char* pidConfigurationType =
    "xyz.openbmc_project.Configuration.Pid";
constexpr const char* settingsDaemon = "xyz.openbmc_project.Settings";
constexpr const char* cfmSettingPath = "/xyz/openbmc_project/control/cfm_limit";
constexpr const char* cfmSettingIface = "xyz.openbmc_project.Control.CFMLimit";

static constexpr double cfmMaxReading = 255;
static constexpr double cfmMinReading = 0;

static constexpr size_t minSystemCfm = 50;

constexpr const auto monitorTypes{
    std::to_array<const char*>({exitAirType, cfmType})};

static std::vector<std::shared_ptr<CFMSensor>> cfmSensors;

static void setupSensorMatch(
    std::vector<sdbusplus::bus::match_t>& matches, sdbusplus::bus_t& connection,
    const std::string& type,
    std::function<void(const double&, sdbusplus::message_t&)>&& callback)
{
    std::function<void(sdbusplus::message_t & message)> eventHandler =
        [callback{std::move(callback)}](sdbusplus::message_t& message) {
            std::string objectName;
            boost::container::flat_map<std::string,
                                       std::variant<double, int64_t>>
                values;
            message.read(objectName, values);
            auto findValue = values.find("Value");
            if (findValue == values.end())
            {
                return;
            }
            double value =
                std::visit(VariantToDoubleVisitor(), findValue->second);
            if (std::isnan(value))
            {
                return;
            }

            callback(value, message);
        };
    matches.emplace_back(
        connection,
        "type='signal',"
        "member='PropertiesChanged',interface='org."
        "freedesktop.DBus.Properties',path_"
        "namespace='/xyz/openbmc_project/sensors/" +
            std::string(type) + "',arg0='xyz.openbmc_project.Sensor.Value'",
        std::move(eventHandler));
}

static void setMaxPWM(const std::shared_ptr<sdbusplus::asio::connection>& conn,
                      double value)
{
    conn->async_method_call(
        [conn,
         value](const boost::system::error_code ec, const GetSubTreeType& ret) {
            if (ec)
            {
                lg2::error("Error calling mapper");
                return;
            }
            for (const auto& [path, objDict] : ret)
            {
                if (objDict.empty())
                {
                    return;
                }
                const std::string& owner = objDict.begin()->first;

                conn->async_method_call(
                    [conn, value, owner,
                     path{path}](const boost::system::error_code ec,
                                 const std::variant<std::string>& classType) {
                        if (ec)
                        {
                            lg2::error("Error getting pid class");
                            return;
                        }
                        const auto* classStr =
                            std::get_if<std::string>(&classType);
                        if (classStr == nullptr || *classStr != "fan")
                        {
                            return;
                        }
                        conn->async_method_call(
                            [](boost::system::error_code& ec) {
                                if (ec)
                                {
                                    lg2::error("Error setting pid class");
                                    return;
                                }
                            },
                            owner, path, "org.freedesktop.DBus.Properties",
                            "Set", pidConfigurationType, "OutLimitMax",
                            std::variant<double>(value));
                    },
                    owner, path, "org.freedesktop.DBus.Properties", "Get",
                    pidConfigurationType, "Class");
            }
        },
        mapper::busName, mapper::path, mapper::interface, mapper::subtree, "/",
        0, std::array<std::string, 1>{pidConfigurationType});
}

CFMSensor::CFMSensor(std::shared_ptr<sdbusplus::asio::connection>& conn,
                     const std::string& sensorName,
                     const std::string& sensorConfiguration,
                     sdbusplus::asio::object_server& objectServer,
                     std::vector<thresholds::Threshold>&& thresholdData,
                     std::shared_ptr<ExitAirTempSensor>& parent) :
    Sensor(escapeName(sensorName), std::move(thresholdData),
           sensorConfiguration, "CFMSensor", false, false, cfmMaxReading,
           cfmMinReading, conn, PowerState::on),
    parent(parent), objServer(objectServer)
{
    sensorInterface = objectServer.add_interface(
        "/xyz/openbmc_project/sensors/airflow/" + name,
        "xyz.openbmc_project.Sensor.Value");

    for (const auto& threshold : thresholds)
    {
        std::string interface = thresholds::getInterface(threshold.level);
        thresholdInterfaces[static_cast<size_t>(threshold.level)] =
            objectServer.add_interface(
                "/xyz/openbmc_project/sensors/airflow/" + name, interface);
    }

    association = objectServer.add_interface(
        "/xyz/openbmc_project/sensors/airflow/" + name, association::interface);

    setInitialProperties(sensor_paths::unitCFM);

    pwmLimitIface =
        objectServer.add_interface("/xyz/openbmc_project/control/pwm_limit",
                                   "xyz.openbmc_project.Control.PWMLimit");
    cfmLimitIface =
        objectServer.add_interface("/xyz/openbmc_project/control/MaxCFM",
                                   "xyz.openbmc_project.Control.CFMLimit");
}

void CFMSensor::setupMatches()
{
    std::weak_ptr<CFMSensor> weakRef = weak_from_this();
    setupSensorMatch(
        matches, *dbusConnection, "fan_tach",
        [weakRef](const double& value, sdbusplus::message_t& message) {
            auto self = weakRef.lock();
            if (!self)
            {
                return;
            }
            self->tachReadings[message.get_path()] = value;
            if (self->tachRanges.find(message.get_path()) ==
                self->tachRanges.end())
            {
                // calls update reading after updating ranges
                self->addTachRanges(message.get_sender(), message.get_path());
            }
            else
            {
                self->updateReading();
            }
        });

    dbusConnection->async_method_call(
        [weakRef](const boost::system::error_code ec,
                  const std::variant<double> cfmVariant) {
            auto self = weakRef.lock();
            if (!self)
            {
                return;
            }

            uint64_t maxRpm = 100;
            if (!ec)
            {
                const auto* cfm = std::get_if<double>(&cfmVariant);
                if (cfm != nullptr && *cfm >= minSystemCfm)
                {
                    maxRpm = self->getMaxRpm(*cfm);
                }
            }
            self->pwmLimitIface->register_property("Limit", maxRpm);
            self->pwmLimitIface->initialize();
            setMaxPWM(self->dbusConnection, maxRpm);
        },
        settingsDaemon, cfmSettingPath, "org.freedesktop.DBus.Properties",
        "Get", cfmSettingIface, "Limit");

    matches.emplace_back(
        *dbusConnection,
        "type='signal',"
        "member='PropertiesChanged',interface='org."
        "freedesktop.DBus.Properties',path='" +
            std::string(cfmSettingPath) + "',arg0='" +
            std::string(cfmSettingIface) + "'",
        [weakRef](sdbusplus::message_t& message) {
            auto self = weakRef.lock();
            if (!self)
            {
                return;
            }
            boost::container::flat_map<std::string, std::variant<double>>
                values;
            std::string objectName;
            message.read(objectName, values);
            const auto findValue = values.find("Limit");
            if (findValue == values.end())
            {
                return;
            }
            auto* const reading = std::get_if<double>(&(findValue->second));
            if (reading == nullptr)
            {
                lg2::error("Got CFM Limit of wrong type");
                return;
            }
            if (*reading < minSystemCfm && *reading != 0)
            {
                lg2::error("Illegal CFM setting detected");
                return;
            }
            uint64_t maxRpm = self->getMaxRpm(*reading);
            self->pwmLimitIface->set_property("Limit", maxRpm);
            setMaxPWM(self->dbusConnection, maxRpm);
        });
}

CFMSensor::~CFMSensor()
{
    for (const auto& iface : thresholdInterfaces)
    {
        objServer.remove_interface(iface);
    }
    objServer.remove_interface(sensorInterface);
    objServer.remove_interface(association);
    objServer.remove_interface(cfmLimitIface);
    objServer.remove_interface(pwmLimitIface);
}

void CFMSensor::createMaxCFMIface()
{
    cfmLimitIface->register_property("Limit", c2 * maxCFM * tachs.size());
    cfmLimitIface->initialize();
}

void CFMSensor::addTachRanges(const std::string& serviceName,
                              const std::string& path)
{
    std::weak_ptr<CFMSensor> weakRef = weak_from_this();
    dbusConnection->async_method_call(
        [weakRef, path](const boost::system::error_code ec,
                        const SensorBaseConfigMap& data) {
            if (ec)
            {
                lg2::error("Error getting properties from '{PATH}'", "PATH",
                           path);
                return;
            }
            auto self = weakRef.lock();
            if (!self)
            {
                return;
            }
            double max = loadVariant<double>(data, "MaxValue");
            double min = loadVariant<double>(data, "MinValue");
            self->tachRanges[path] = std::make_pair(min, max);
            self->updateReading();
        },
        serviceName, path, "org.freedesktop.DBus.Properties", "GetAll",
        "xyz.openbmc_project.Sensor.Value");
}

void CFMSensor::checkThresholds()
{
    thresholds::checkThresholds(this);
}

void CFMSensor::updateReading()
{
    double val = 0.0;
    if (calculate(val))
    {
        if (value != val && parent)
        {
            parent->updateReading();
        }
        updateValue(val);
    }
    else
    {
        updateValue(std::numeric_limits<double>::quiet_NaN());
    }
}

uint64_t CFMSensor::getMaxRpm(uint64_t cfmMaxSetting) const
{
    uint64_t pwmPercent = 100;
    double totalCFM = std::numeric_limits<double>::max();
    if (cfmMaxSetting == 0)
    {
        return pwmPercent;
    }

    bool firstLoop = true;
    while (totalCFM > cfmMaxSetting)
    {
        if (firstLoop)
        {
            firstLoop = false;
        }
        else
        {
            pwmPercent--;
        }

        double ci = 0;
        if (pwmPercent == 0)
        {
            ci = 0;
        }
        else if (pwmPercent < tachMinPercent)
        {
            ci = c1;
        }
        else if (pwmPercent > tachMaxPercent)
        {
            ci = c2;
        }
        else
        {
            ci = c1 + (((c2 - c1) * (pwmPercent - tachMinPercent)) /
                       (tachMaxPercent - tachMinPercent));
        }

        // Now calculate the CFM for this tach
        // CFMi = Ci * Qmaxi * TACHi
        totalCFM = ci * maxCFM * pwmPercent;
        totalCFM *= tachs.size();
        // divide by 100 since pwm is in percent
        totalCFM /= 100;

        if (pwmPercent <= 0)
        {
            break;
        }
    }

    return pwmPercent;
}

bool CFMSensor::calculate(double& value)
{
    double totalCFM = 0;
    for (const std::string& tachName : tachs)
    {
        auto findReading = std::find_if(
            tachReadings.begin(), tachReadings.end(),
            [&](const auto& item) { return item.first.ends_with(tachName); });
        auto findRange = std::find_if(
            tachRanges.begin(), tachRanges.end(),
            [&](const auto& item) { return item.first.ends_with(tachName); });
        if (findReading == tachReadings.end())
        {
            lg2::debug("Can't find '{NAME}' in readings", "NAME", tachName);
            continue; // haven't gotten a reading
        }

        if (findRange == tachRanges.end())
        {
            lg2::error("Can't find '{NAME}' in ranges", "NAME", tachName);
            return false; // haven't gotten a max / min
        }

        // avoid divide by 0
        if (findRange->second.second == 0)
        {
            lg2::error("Tach Max Set to 0, tachName: '{NAME}'", "NAME",
                       tachName);
            return false;
        }

        double rpm = findReading->second;

        // for now assume the min for a fan is always 0, divide by max to get
        // percent and mult by 100
        rpm /= findRange->second.second;
        rpm *= 100;

        lg2::debug("Tach '{NAME}' at '{RPM}'", "NAME", tachName, "RPM", rpm);

        // Do a linear interpolation to get Ci
        // Ci = C1 + (C2 - C1)/(RPM2 - RPM1) * (TACHi - TACH1)

        double ci = 0;
        if (rpm == 0)
        {
            ci = 0;
        }
        else if (rpm < tachMinPercent)
        {
            ci = c1;
        }
        else if (rpm > tachMaxPercent)
        {
            ci = c2;
        }
        else
        {
            ci = c1 + (((c2 - c1) * (rpm - tachMinPercent)) /
                       (tachMaxPercent - tachMinPercent));
        }

        // Now calculate the CFM for this tach
        // CFMi = Ci * Qmaxi * TACHi
        totalCFM += ci * maxCFM * rpm;
        lg2::debug(
            "totalCFM = {CFM}, Ci = {CI}, MaxCFM = {MAXCFM}, rpm = {RPM}, c1 = {C1}"
            ", c2 = {C2}, max = {MAX}, min = {MIN}",
            "CFM", totalCFM, "CI", ci, "MAXCFM", maxCFM, "RPM", rpm, "C1", c1,
            "C2", c2, "MAX", tachMaxPercent, "MIN", tachMinPercent);
    }

    // divide by 100 since rpm is in percent
    value = totalCFM / 100;
    lg2::debug("cfm value = {VALUE}", "VALUE", value);
    return true;
}

static constexpr double exitAirMaxReading = 127;
static constexpr double exitAirMinReading = -128;
ExitAirTempSensor::ExitAirTempSensor(
    std::shared_ptr<sdbusplus::asio::connection>& conn,
    const std::string& sensorName, const std::string& sensorConfiguration,
    sdbusplus::asio::object_server& objectServer,
    std::vector<thresholds::Threshold>&& thresholdData) :
    Sensor(escapeName(sensorName), std::move(thresholdData),
           sensorConfiguration, "ExitAirTemp", false, false, exitAirMaxReading,
           exitAirMinReading, conn, PowerState::on),
    objServer(objectServer)
{
    sensorInterface = objectServer.add_interface(
        "/xyz/openbmc_project/sensors/temperature/" + name,
        "xyz.openbmc_project.Sensor.Value");

    for (const auto& threshold : thresholds)
    {
        std::string interface = thresholds::getInterface(threshold.level);
        thresholdInterfaces[static_cast<size_t>(threshold.level)] =
            objectServer.add_interface(
                "/xyz/openbmc_project/sensors/temperature/" + name, interface);
    }
    association = objectServer.add_interface(
        "/xyz/openbmc_project/sensors/temperature/" + name,
        association::interface);
    setInitialProperties(sensor_paths::unitDegreesC);
}

ExitAirTempSensor::~ExitAirTempSensor()
{
    for (const auto& iface : thresholdInterfaces)
    {
        objServer.remove_interface(iface);
    }
    objServer.remove_interface(sensorInterface);
    objServer.remove_interface(association);
}

void ExitAirTempSensor::setupMatches()
{
    constexpr const auto matchTypes{
        std::to_array<const char*>({"power", inletTemperatureSensor})};

    std::weak_ptr<ExitAirTempSensor> weakRef = weak_from_this();
    for (const std::string type : matchTypes)
    {
        setupSensorMatch(
            matches, *dbusConnection, type,
            [weakRef,
             type](const double& value, sdbusplus::message_t& message) {
                auto self = weakRef.lock();
                if (!self)
                {
                    return;
                }
                if (type == "power")
                {
                    std::string path = message.get_path();
                    if (path.find("PS") != std::string::npos &&
                        path.ends_with("Input_Power"))
                    {
                        self->powerReadings[message.get_path()] = value;
                    }
                }
                else if (type == inletTemperatureSensor)
                {
                    self->inletTemp = value;
                }
                self->updateReading();
            });
    }
    dbusConnection->async_method_call(
        [weakRef](boost::system::error_code ec,
                  const std::variant<double>& value) {
            if (ec)
            {
                // sensor not ready yet
                return;
            }
            auto self = weakRef.lock();
            if (!self)
            {
                return;
            }
            self->inletTemp = std::visit(VariantToDoubleVisitor(), value);
        },
        "xyz.openbmc_project.HwmonTempSensor",
        std::string("/xyz/openbmc_project/sensors/") + inletTemperatureSensor,
        properties::interface, properties::get, sensorValueInterface, "Value");
    dbusConnection->async_method_call(
        [weakRef](boost::system::error_code ec, const GetSubTreeType& subtree) {
            if (ec)
            {
                lg2::error("Error contacting mapper");
                return;
            }
            auto self = weakRef.lock();
            if (!self)
            {
                return;
            }
            for (const auto& [path, matches] : subtree)
            {
                size_t lastSlash = path.rfind('/');
                if (lastSlash == std::string::npos ||
                    lastSlash == path.size() || matches.empty())
                {
                    continue;
                }
                std::string sensorName = path.substr(lastSlash + 1);
                if (sensorName.starts_with("PS") &&
                    sensorName.ends_with("Input_Power"))
                {
                    // lambda capture requires a proper variable (not a
                    // structured binding)
                    const std::string& cbPath = path;
                    self->dbusConnection->async_method_call(
                        [weakRef, cbPath](boost::system::error_code ec,
                                          const std::variant<double>& value) {
                            if (ec)
                            {
                                lg2::error("Error getting value from '{PATH}'",
                                           "PATH", cbPath);
                            }
                            auto self = weakRef.lock();
                            if (!self)
                            {
                                return;
                            }
                            double reading =
                                std::visit(VariantToDoubleVisitor(), value);
                            lg2::debug("'{PATH}' reading '{VALUE}'", "PATH",
                                       cbPath, "VALUE", reading);
                            self->powerReadings[cbPath] = reading;
                        },
                        matches[0].first, cbPath, properties::interface,
                        properties::get, sensorValueInterface, "Value");
                }
            }
        },
        mapper::busName, mapper::path, mapper::interface, mapper::subtree,
        "/xyz/openbmc_project/sensors/power", 0,
        std::array<const char*, 1>{sensorValueInterface});
}

void ExitAirTempSensor::updateReading()
{
    double val = 0.0;
    if (calculate(val))
    {
        val = std::floor(val + 0.5);
        updateValue(val);
    }
    else
    {
        updateValue(std::numeric_limits<double>::quiet_NaN());
    }
}

double ExitAirTempSensor::getTotalCFM()
{
    double sum = 0;
    for (auto& sensor : cfmSensors)
    {
        double reading = 0;
        if (!sensor->calculate(reading))
        {
            return -1;
        }
        sum += reading;
    }

    return sum;
}

bool ExitAirTempSensor::calculate(double& val)
{
    constexpr size_t maxErrorPrint = 5;
    static bool firstRead = false;
    static size_t errorPrint = maxErrorPrint;

    double cfm = getTotalCFM();
    if (cfm <= 0)
    {
        lg2::error("Error getting cfm");
        return false;
    }

    // Though cfm is not expected to be less than qMin normally,
    // it is not a hard limit for exit air temp calculation.
    // 50% qMin is chosen as a generic limit between providing
    // a valid derived exit air temp and reporting exit air temp not available.
    constexpr const double cfmLimitFactor = 0.5;
    if (cfm < (qMin * cfmLimitFactor))
    {
        if (errorPrint > 0)
        {
            errorPrint--;
            lg2::error("cfm '{CFM}' is too low, expected qMin '{QMIN}'", "CFM",
                       cfm, "QMIN", qMin);
        }
        val = 0;
        return false;
    }

    // if there is an error getting inlet temp, return error
    if (std::isnan(inletTemp))
    {
        if (errorPrint > 0)
        {
            errorPrint--;
            lg2::error("Cannot get inlet temp");
        }
        val = 0;
        return false;
    }

    // if fans are off, just make the exit temp equal to inlet
    if (!isPowerOn())
    {
        val = inletTemp;
        return true;
    }

    double totalPower = 0;
    for (const auto& [path, reading] : powerReadings)
    {
        if (std::isnan(reading))
        {
            continue;
        }
        totalPower += reading;
    }

    // Calculate power correction factor
    // Ci = CL + (CH - CL)/(QMax - QMin) * (CFM - QMin)
    double powerFactor = 0.0;
    if (cfm <= qMin)
    {
        powerFactor = powerFactorMin;
    }
    else if (cfm >= qMax)
    {
        powerFactor = powerFactorMax;
    }
    else
    {
        powerFactor = powerFactorMin + ((powerFactorMax - powerFactorMin) /
                                        (qMax - qMin) * (cfm - qMin));
    }

    totalPower *= powerFactor;
    totalPower += pOffset;

    if (totalPower == 0)
    {
        if (errorPrint > 0)
        {
            errorPrint--;
            lg2::error("total power 0");
        }
        val = 0;
        return false;
    }

    lg2::debug(
        "Power Factor: {POWER_FACTOR}, Inlet Temp: {INLET_TEMP}, Total Power: {TOTAL_POWER}",
        "POWER_FACTOR", powerFactor, "INLET_TEMP", inletTemp, "TOTAL_POWER",
        totalPower);

    // Calculate the exit air temp
    // Texit = Tfp + (1.76 * TotalPower / CFM * Faltitude)
    double reading = 1.76 * totalPower * altitudeFactor;
    reading /= cfm;
    reading += inletTemp;

    lg2::debug("Reading 1: '{VALUE}'", "VALUE", reading);

    // Now perform the exponential average
    // Calculate alpha based on SDR values and CFM
    // Ai = As + (Af - As)/(QMax - QMin) * (CFM - QMin)

    double alpha = 0.0;
    if (cfm < qMin)
    {
        alpha = alphaS;
    }
    else if (cfm >= qMax)
    {
        alpha = alphaF;
    }
    else
    {
        alpha = alphaS + ((alphaF - alphaS) * (cfm - qMin) / (qMax - qMin));
    }

    auto time = std::chrono::steady_clock::now();
    if (!firstRead)
    {
        firstRead = true;
        lastTime = time;
        lastReading = reading;
    }
    double alphaDT =
        std::chrono::duration_cast<std::chrono::seconds>(time - lastTime)
            .count() *
        alpha;

    // cap at 1.0 or the below fails
    if (alphaDT > 1.0)
    {
        alphaDT = 1.0;
    }

    lg2::debug("AlphaDT: '{ADT}'", "ADT", alphaDT);

    reading = ((reading * alphaDT) + (lastReading * (1.0 - alphaDT)));

    lg2::debug("Reading 2: '{VALUE}'", "VALUE", reading);

    val = reading;
    lastReading = reading;
    lastTime = time;
    errorPrint = maxErrorPrint;
    return true;
}

void ExitAirTempSensor::checkThresholds()
{
    thresholds::checkThresholds(this);
}

static void loadVariantPathArray(const SensorBaseConfigMap& data,
                                 const std::string& key,
                                 std::vector<std::string>& resp)
{
    auto it = data.find(key);
    if (it == data.end())
    {
        lg2::error("Configuration missing '{KEY}'", "KEY", key);
        throw std::invalid_argument("Key Missing");
    }
    BasicVariantType copy = it->second;
    std::vector<std::string> config = std::get<std::vector<std::string>>(copy);
    for (auto& str : config)
    {
        boost::replace_all(str, " ", "_");
    }
    resp = std::move(config);
}

void createSensor(sdbusplus::asio::object_server& objectServer,
                  std::shared_ptr<ExitAirTempSensor>& exitAirSensor,
                  std::shared_ptr<sdbusplus::asio::connection>& dbusConnection)
{
    if (!dbusConnection)
    {
        lg2::error("Connection not created");
        return;
    }
    auto getter = std::make_shared<GetSensorConfiguration>(
        dbusConnection, [&objectServer, &dbusConnection,
                         &exitAirSensor](const ManagedObjectType& resp) {
            cfmSensors.clear();
            for (const auto& [path, interfaces] : resp)
            {
                for (const auto& [intf, cfg] : interfaces)
                {
                    if (intf == configInterfaceName(exitAirType))
                    {
                        // thresholds should be under the same path
                        std::vector<thresholds::Threshold> sensorThresholds;
                        parseThresholdsFromConfig(interfaces, sensorThresholds);

                        std::string name =
                            loadVariant<std::string>(cfg, "Name");
                        exitAirSensor = nullptr;
                        exitAirSensor = std::make_shared<ExitAirTempSensor>(
                            dbusConnection, name, path.str, objectServer,
                            std::move(sensorThresholds));
                        exitAirSensor->powerFactorMin =
                            loadVariant<double>(cfg, "PowerFactorMin");
                        exitAirSensor->powerFactorMax =
                            loadVariant<double>(cfg, "PowerFactorMax");
                        exitAirSensor->qMin = loadVariant<double>(cfg, "QMin");
                        exitAirSensor->qMax = loadVariant<double>(cfg, "QMax");
                        exitAirSensor->alphaS =
                            loadVariant<double>(cfg, "AlphaS");
                        exitAirSensor->alphaF =
                            loadVariant<double>(cfg, "AlphaF");
                    }
                    else if (intf == configInterfaceName(cfmType))
                    {
                        // thresholds should be under the same path
                        std::vector<thresholds::Threshold> sensorThresholds;
                        parseThresholdsFromConfig(interfaces, sensorThresholds);
                        std::string name =
                            loadVariant<std::string>(cfg, "Name");
                        auto sensor = std::make_shared<CFMSensor>(
                            dbusConnection, name, path.str, objectServer,
                            std::move(sensorThresholds), exitAirSensor);
                        loadVariantPathArray(cfg, "Tachs", sensor->tachs);
                        sensor->maxCFM = loadVariant<double>(cfg, "MaxCFM");

                        // change these into percent upon getting the data
                        sensor->c1 = loadVariant<double>(cfg, "C1") / 100;
                        sensor->c2 = loadVariant<double>(cfg, "C2") / 100;
                        sensor->tachMinPercent =
                            loadVariant<double>(cfg, "TachMinPercent");
                        sensor->tachMaxPercent =
                            loadVariant<double>(cfg, "TachMaxPercent");
                        sensor->createMaxCFMIface();
                        sensor->setupMatches();

                        cfmSensors.emplace_back(std::move(sensor));
                    }
                }
            }
            if (exitAirSensor)
            {
                exitAirSensor->setupMatches();
                exitAirSensor->updateReading();
            }
        });
    getter->getConfiguration(
        std::vector<std::string>(monitorTypes.begin(), monitorTypes.end()));
}

int main()
{
    boost::asio::io_context io;
    auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
    sdbusplus::asio::object_server objectServer(systemBus, true);
    objectServer.add_manager("/xyz/openbmc_project/sensors");
    systemBus->request_name("xyz.openbmc_project.ExitAirTempSensor");
    std::shared_ptr<ExitAirTempSensor> sensor =
        nullptr; // wait until we find the config

    boost::asio::post(io,
                      [&]() { createSensor(objectServer, sensor, systemBus); });

    boost::asio::steady_timer configTimer(io);

    std::function<void(sdbusplus::message_t&)> eventHandler =
        [&](sdbusplus::message_t&) {
            configTimer.expires_after(std::chrono::seconds(1));
            // create a timer because normally multiple properties change
            configTimer.async_wait([&](const boost::system::error_code& ec) {
                if (ec == boost::asio::error::operation_aborted)
                {
                    return; // we're being canceled
                }
                createSensor(objectServer, sensor, systemBus);
                if (!sensor)
                {
                    lg2::info("Configuration not detected");
                }
            });
        };
    std::vector<std::unique_ptr<sdbusplus::bus::match_t>> matches =
        setupPropertiesChangedMatches(*systemBus, monitorTypes, eventHandler);

    setupManufacturingModeMatch(*systemBus);
    io.run();
    return 0;
}
